﻿using System.Data.Linq;
//using System.Data.Linq.Mapping;
using System.Linq;
using System;
using System.Collections.Generic;
using System.Collections;
using System.Reflection;
using System.Windows;
using System.Windows.Controls;
using PDO.DataPersistence.Core;
using System.Linq.Dynamic;

namespace PDO.UI.Controls
{
    /// <summary>
    /// Interaction logic for DataExplorerControl.xaml
    /// </summary>
    public partial class DataExplorerControl : UserControl
    {

        private const int MAX_DISPLAYED_LIST_ITEMS = 100;

        #region Private members

        private readonly object _dummyNode = null;

        #endregion Private members

        #region Dependency Properties

        public static DependencyProperty SelectedItemProperty =
         DependencyProperty.Register("SelectedItem", typeof(DataExplorerItemInfo), typeof(DataExplorerControl));

        #endregion

        public DataExplorerControl()
        {
            InitializeComponent();

            _treeView.SelectedItemChanged += new RoutedPropertyChangedEventHandler<object>(_treeView_SelectedTreeItemChanged);
        }


        public void Initialize(PersistableDataObjectClassInfo pdoinfo, PropertyInfo pi, string propertyValue)
        {
            try
            {
                // Get Table property from PersistableDataObject instance we're querying on.
                PropertyInfo tableProperty = pdoinfo.ClassType.GetProperty("Table");
                MethodInfo tablePropertyGetMethod = tableProperty.GetGetMethod();
                object[] emptyArgs = { };
                ITable it = tablePropertyGetMethod.Invoke(null, emptyArgs) as ITable;

                IPersistableDataObjectList resultList = null;
                if (pi == null || string.IsNullOrEmpty(propertyValue))
                {
                    // If we don't have a property or property value to create a query for, just return all 
                    // items of specfied type.

                    Type pdoType = pdoinfo.ClassType;
                    Type[] typeSignature = { typeof(DataSession) };

                    // Call GetAll method on object type
                    MethodInfo listByQueryMethod = pdoType.GetMethod("GetAll", typeSignature);
                    

                    // Get result list and display
                    object[] args = { DataSession.DefaultSession };
                    resultList = listByQueryMethod.Invoke(null, args) as IPersistableDataObjectList;
                }
                else
                {
                    // Construct a query based on provided property and property value.

                    Type pdoType = typeof(PDOMapper);
                    Type[] typeSignature = { typeof(PersistableDataObject), typeof(string), typeof(string), typeof(DataSession), typeof(IQueryable) };

                    // Construct Dynamic Linq query
                    string qformat = string.Format("{0}.ToString() == @0", pi.Name);
                    var query = it.Where(qformat, propertyValue);

                    // Get generic GetListByQuery method from PDOMapper
                    MethodInfo listByQueryMethod = pdoType.GetMethod("GetListByQuery", typeSignature);
                    MethodInfo listByQueryMethodConstructed = listByQueryMethod.MakeGenericMethod(pdoinfo.ClassType);

                    // Get result list and display
                    object[] args = { null, null, null, DataSession.DefaultSession, query.AsQueryable() };
                    resultList = listByQueryMethodConstructed.Invoke(null, args) as IPersistableDataObjectList;
                }

                Initialize(resultList);

            }
            catch (Exception ex)
            {
                _treeView.Items.Clear(); // remove any previous nodes

                TreeViewItem pitem = new TreeViewItem();
                pitem.Header = ex.ToString();
                _treeView.Items.Add(pitem);
            }
        }

        public void Initialize(IPersistableDataObjectList pdoList)
        {
            _treeView.Items.Clear();

            if (pdoList.Count <= 0)
            {
                TreeViewItem pitem = new TreeViewItem();
                pitem.Header = "No records found.";
                _treeView.Items.Add(pitem);
                return;
            }

            if (pdoList.Count == 1)
            {
                Initialize(pdoList[0] as PersistableDataObject);
                return;
            }


            try
            {
                TreeViewItem pitem = new TreeViewItem();
                pitem.Header = String.Format("{0} [Count={1}]", pdoList[0].GetType().Name, pdoList.Count);
                pitem.Tag = new DataExplorerItemInfo() { PersistableData = pdoList }; // no parent PropertyPath for root
                pitem.Expanded += folder_Expanded;
                pitem.Items.Add(_dummyNode);

                _treeView.Items.Add(pitem);
            }
            catch (Exception ex)
            {
                TreeViewItem pitem = new TreeViewItem();
                pitem.Header = ex.ToString();
                _treeView.Items.Add(pitem);
            }
        }

        public void Initialize(PersistableDataObject pdo)
        {
            _treeView.Items.Clear(); // remove any previous nodes

            try
            {
                TreeViewItem pitem = new TreeViewItem();
                pitem.Header = pdo.GetType().Name;
                pitem.Tag = new DataExplorerItemInfo() { PersistableData = pdo }; // no parent PropertyPath for root
                pitem.Expanded += folder_Expanded;
                pitem.Items.Add(_dummyNode);

                _treeView.Items.Add(pitem);
            }
            catch (Exception ex)
            {
                TreeViewItem pitem = new TreeViewItem();
                pitem.Header = ex.ToString();
                _treeView.Items.Add(pitem);
            }
        }


        /// <summary>
        /// This a little cumbersome, but it's how things work with WPF tree controls.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void _treeView_SelectedTreeItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
        {
            if (null != ((TreeView)sender).SelectedItem)
            {
                SelectedTreeItem = ((TreeViewItem)((TreeView)sender).SelectedItem).Tag as DataExplorerItemInfo;
            }
            else
            {
                SelectedTreeItem = null;
            }
        }


        // CLR dependency property
        public DataExplorerItemInfo SelectedTreeItem
        {
            get
            {
                return ((DataExplorerItemInfo)(GetValue(SelectedItemProperty)));
            }
            set
            {
                SetValue(SelectedItemProperty, value);
            }
        }


        /// <summary>
        /// Handle tree view folder expanded event.
        /// </summary>
        void folder_Expanded(object sender, RoutedEventArgs e)
        {
            TreeViewItem item = (TreeViewItem)sender;
            if (item.Items.Count == 1 && item.Items[0] == _dummyNode)
            {
                try
                {
                    DataExplorerItemInfo itemInfo = item.Tag as DataExplorerItemInfo;

                    if (itemInfo.PersistableData is PersistableDataObject)
                    {
                        AddPDOItems(itemInfo.PersistableData as PersistableDataObject, item, itemInfo.PropertyPath);
                    }
                    else if (itemInfo.PersistableData is IList)
                    {
                        AddPDOItems(itemInfo.PersistableData as IList, item, itemInfo.PropertyPath);
                    }
                    else
                    {
                        AddPDOItems(itemInfo.PersistableData, item, itemInfo.PropertyPath);
                    }
                }
                catch (Exception ex)
                {
                    item.Items.Add(new TreeViewItem() { Header = ex.ToString() });
                }
            }
        }

        /// <summary>
        /// Add a TreeViewItem for each PDO property.
        /// </summary>
        /// <param name="pdo">PersistableDataObject</param>
        /// <param name="items">tree view items collection</param>
        private void AddPDOItems(PersistableDataObject pdo, TreeViewItem rootItem, string parentPath)
        {
            ItemCollection items = rootItem.Items;
            TreeViewItem rootItemParent = rootItem.Parent as TreeViewItem;

            items.Clear(); // remove _dummyNode

            if (!string.IsNullOrEmpty(parentPath))
            {
                parentPath += ".";
            }

            if (null != pdo)
            {
                IEnumerable<PropertyInfo> piArray = pdo.GetType().GetProperties().OrderBy(cmp => cmp, new PropertyInfoComparer());

                foreach (PropertyInfo pi in piArray)
                {
                    if (ReflectionHelper.IsXmlSerializable(pi))
                    {
                        TreeViewItem item = new TreeViewItem();

                        if (null != pi.PropertyType.GetInterface(typeof(IPersistableDataObjectList).Name))
                        {
                            int foo = 2;
                        }

                        if (pi.PropertyType.IsSubclassOf(typeof(IPersistableDataObjectList)))
                        {
                            int foo = 3;
                        }

                        object propertyObject = null;
                        bool errorTrap = false;
                        try
                        {
                            propertyObject = ReflectionHelper.GetObjectPropertyValue(pdo, pi);
                        }
                        catch (Exception ex)
                        {
                            errorTrap = true;
                            item.Header = pi.Name + " = Error: " + ex.ToString();
                        }

                        if (errorTrap == false)
                        {
                            IList list = propertyObject as IList;

                            if (pi.PropertyType.IsSubclassOf(typeof(PersistableDataObject)))
                            {
                                item.Tag = new DataExplorerItemInfo() { PersistableData = propertyObject, PropertyPath = parentPath + pi.Name };
                                PersistableDataObject childPdo = propertyObject as PersistableDataObject;
                                item.Header = pi.Name + ((null == childPdo) ? " <null>" : (childPdo.IsNew ? " <empty>" : string.Empty));
                                item.Items.Add(_dummyNode);
                                item.Expanded += folder_Expanded;
                            }
                            else if (null != list)
                            {
                                item.Tag = new DataExplorerItemInfo() { PersistableData = propertyObject, PropertyPath = parentPath + pi.Name };
                                item.Header = pi.Name + " [Count=" + list.Count + "]";
                                if (list.Count > 0)
                                {
                                    item.Items.Add(_dummyNode);
                                    item.Expanded += folder_Expanded;
                                }
                            }
                            else  // leaf node
                            {
                                item.Header = pi.Name + " = " + propertyObject;
                                item.Tag = new DataExplorerItemInfo() { PersistableData = pdo, PropertyPath = parentPath + pi.Name };
                            }
                        }

                        // Could check writeable property -- pi.CanWrite
                        items.Add(item);
                    }
                }
            }
        }

        /// <summary>
        /// Add a TreeViewItem for each PDO property.
        /// </summary>
        /// <param name="pdo">PersistableDataObject</param>
        /// <param name="items">tree view items collection</param>
        private void AddPDOItems(Object pdo, TreeViewItem rootItem, string parentPath)
        {
            ItemCollection items = rootItem.Items;
            TreeViewItem rootItemParent = rootItem.Parent as TreeViewItem;

            items.Clear(); // remove _dummyNode

            if (!string.IsNullOrEmpty(parentPath))
            {
                parentPath += ".";
            }

            if (null != pdo)
            {
                IEnumerable<PropertyInfo> piArray = pdo.GetType().GetProperties().OrderBy(cmp => cmp, new PropertyInfoComparer());

                if (piArray.Count() == 0)
                {
                    TreeViewItem item = new TreeViewItem();
                    item.Header = "value: " + pdo;
                    item.Tag = new DataExplorerItemInfo() { PersistableData = pdo };
                    items.Add(item);

                }
                else
                {
                    foreach (PropertyInfo pi in piArray)
                    {
                        if (ReflectionHelper.IsXmlSerializable(pi))
                        {
                            TreeViewItem item = new TreeViewItem();
                            object propertyObject = ReflectionHelper.GetObjectPropertyValue(pdo, pi);

                            IList list = propertyObject as IList;

                            if (pi.PropertyType.IsSubclassOf(typeof(PersistableDataObject)))
                            {
                                item.Tag = new DataExplorerItemInfo() { PersistableData = propertyObject, PropertyPath = parentPath + pi.Name };
                                PersistableDataObject childPdo = propertyObject as PersistableDataObject;
                                item.Header = pi.Name + ((null == childPdo) ? " <null>" : (childPdo.IsNew ? " <empty>" : string.Empty));
                                item.Items.Add(_dummyNode);
                                item.Expanded += folder_Expanded;
                            }
                            else if (null != list)
                            {
                                item.Tag = new DataExplorerItemInfo() { PersistableData = propertyObject, PropertyPath = parentPath + pi.Name };
                                item.Header = pi.Name + " [Count=" + list.Count + "]";
                                if (list.Count > 0)
                                {
                                    item.Items.Add(_dummyNode);
                                    item.Expanded += folder_Expanded;
                                }
                            }
                            else  // leaf node
                            {
                                item.Header = pi.Name + " = " + propertyObject;
                                item.Tag = new DataExplorerItemInfo() { PersistableData = pdo, PropertyPath = parentPath + pi.Name };
                            }

                            // Could check writeable property -- pi.CanWrite
                            items.Add(item);
                        }
                    }
                }
            }
        }

        /// <summary>
        /// Add a TreeViewItem for each object in list
        /// </summary>
        /// <param name="list">item list</param>
        /// <param name="items">tree view items collection</param>
        private void AddPDOItems(IList list, TreeViewItem rootItem, string parentPath)
        {
            TreeViewItem rootItemParent = rootItem.Parent as TreeViewItem;
            ItemCollection items;


            const string REMAINING_LABEL = " remaining...";

            int start = 0;
            if (rootItem.Header.ToString().Contains(REMAINING_LABEL))
            {
                int i1 = parentPath.LastIndexOf('[');
                int i2 = parentPath.LastIndexOf(']');
                string index = parentPath.Substring(i1 + 1, (i2 - i1) - 1);
                start = Convert.ToInt32(index) + 1;

                parentPath = parentPath.Substring(0, i1);
                rootItemParent.Items.Remove(rootItem); // remove "remaining..." placeholder node
                items = rootItemParent.Items;
            }
            else
            {
                items = rootItem.Items;
                items.Clear(); // remove _dummyNode

            }

            int end = list.Count - start;

            int count = 0;
            for (int i = start; i < end; i++)
            {
                object o = list[i];
                TreeViewItem item = new TreeViewItem();
                item.Header = o.GetType().Name + "[" + i + "]";
                item.Tag = new DataExplorerItemInfo() { PersistableData = o, PropertyPath = parentPath + "[" + i + "]" };

                item.Items.Add(_dummyNode);
                item.Expanded += folder_Expanded;

                items.Add(item);
                count++;

                if (count > MAX_DISPLAYED_LIST_ITEMS)
                {
                    item = new TreeViewItem();
                    int remaining = list.Count - start - count;
                    item.Header = remaining.ToString() + REMAINING_LABEL;
                    item.Tag = new DataExplorerItemInfo() { PersistableData = list, PropertyPath = parentPath + "[" + i + "]" };

                    item.Items.Add(_dummyNode);
                    item.Expanded += folder_Expanded;

                    items.Add(item);
                    break; // exit loop
                }
            }
        }

    }


    #region Helper class

    public class DataExplorerItemInfo
    {
        public object PersistableData { get; set; }
        public string PropertyPath { get; set; }
    }


    #endregion
}
